package org.rea.clock;

/** * An analog clock -- Using Timer and Calendar..// Uses a BufferedImage and antialiasing.
 *
 * The second hand creep is from the Timer, which seems to take
 * a little longer than the requested time to call the listener.
 *  -- Fred Swartz, 1 May 1999, 2002-02-21 from applet to application
 */
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.swing.*;
import javax.swing.event.*;
import java.util.*;

public class ClockAnalogBuf extends JFrame {

    Clock clockFace;

    /**
     * Main
     * @param args
     */
    public static void main(String[] args) {
        JFrame windo = new ClockAnalogBuf();
        windo.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        windo.setVisible(true);
    }

    /**
     * Constructor
     */
    public ClockAnalogBuf() {
        Container content = this.getContentPane();
        content.setLayout(new BorderLayout());
        clockFace = new Clock();
        content.add(clockFace, BorderLayout.CENTER);
        
        this.setTitle("Analog Clock");
        this.pack();
        
        clockFace.start();
    }
}

class Clock extends JPanel {

    private int hours   = 0;
    private int minutes = 0;
    private int seconds = 0;
    private int millis  = 0;

    private static final int   spacing = 10;
    private static final float twoPi = (float)(2.0 * Math.PI);
    private static final float threePi = (float)(3.0 * Math.PI);
    // Angles for the trigonometric functions are measured in radiant.
    // The following in the number of radiant per sec or min.
    private static final float radPerSecMin = (float)(Math.PI / 30.0);

    private int size;       // height and width of clock face
    private int centerX;    // x coord of middle of clock
    private int centerY;    // y coord of middle of clock
    private BufferedImage clockImage;
    private javax.swing.Timer t;

    /**
     * Constructor
     */
    public Clock() {
        this.setPreferredSize(new Dimension(300,300));
        this.setBackground(Color.white);
        this.setForeground(Color.black);

        t = new javax.swing.Timer(1000,
              new ActionListener() {
                  public void actionPerformed(ActionEvent e) {
                      update();
                  }
              });
    }

    /**
     * Replace the default update so that the plain background doesn't get drawn.
     */
    public void update() {
    	/*
    	 * Hmmm, is this the right way to do this? Or do we just
         * call paintComponent?  Does this coalesce calls, not that
         * this updates often enuf for that to be important.
         */
        this.repaint();
    }

    /**
     * start the timer
     */
    public void start() {
        t.start(); 
    }

    /**
     * stio the timer
     */
    public void stop() {
        t.stop();
    }

    /**
     * 
     */
    public void paintComponent(Graphics g) {
        super.paintComponent(g);  // paint background, borders
        Graphics2D g2 = (Graphics2D)g;
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                            RenderingHints.VALUE_ANTIALIAS_ON);

        // The panel may have been resized, get current dimensions
        int w = getWidth();
        int h = getHeight();
        size = ((w<h) ? w : h) - 2*spacing;
        centerX = size/2 + spacing;
        centerY = size/2 + spacing;

        /*
         *  Create the clock face background image if this is the first time,
         *  or if the size of the panel has changed 
         */
        if (clockImage == null
                || clockImage.getWidth() != w
                || clockImage.getHeight() != h) {

            clockImage = (BufferedImage)(this.createImage(w, h));
            // now get a graphics context from this image
            Graphics2D gc = clockImage.createGraphics();
            gc.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                                RenderingHints.VALUE_ANTIALIAS_ON);
            drawClockFace(gc);
        }
        
        // Now get the time and draw the hands.
        Calendar now = Calendar.getInstance();
        hours   = now.get(Calendar.HOUR);
        minutes = now.get(Calendar.MINUTE);
        seconds = now.get(Calendar.SECOND);
        millis  = now.get(Calendar.MILLISECOND);

        // Draw the clock face from the precomputed image
        g2.drawImage(clockImage, null, 0, 0);

        // Draw the clock hands
        drawClockHands(g);
    }


    /**
     * drawClockHands
     */
    private void drawClockHands(Graphics g) {
        int secondRadius = size/2;
        int minuteRadius = secondRadius * 3/4;
        int hourRadius   = secondRadius/2;

        // second hand
        float fseconds = seconds + (float)millis/1000;
        float secondAngle = threePi - (radPerSecMin * fseconds);
        drawRadius(g, centerX, centerY, secondAngle, size/10, secondRadius);

        // minute hand
        float fminutes = (float)(minutes + fseconds/60.0);
        float minuteAngle = threePi - (radPerSecMin * fminutes);
        drawRadius(g, centerX, centerY, minuteAngle, size/10, minuteRadius);

        // hour hand
        float fhours = (float)(hours + fminutes/60.0);
        float hourAngle = threePi - (5 * radPerSecMin * fhours);
        drawRadius(g, centerX, centerY, hourAngle, size/10, hourRadius);
    }

    /**
     * drawClockFace
     * @param g
     */
    private void drawClockFace(Graphics g) {
        // clock face
        g.setColor(new Color(128,196,192));
        g.fillOval(spacing, spacing, size, size);
        g.setColor(Color.black);
        g.drawOval(spacing, spacing, size, size);

        // tick marks
        for (int sec = 0; sec<60; sec++) {
            int ticStart;
            if (sec%5 == 0) {
                ticStart = size/2-10;
            } else {
                ticStart = size/2-5;
            }
            drawRadius(g, centerX, centerY, radPerSecMin*sec, ticStart , size/2);
        }
    }

    /**
     * drawRadius
     * @param g
     * @param x
     * @param y
     * @param angle
     * @param minRadius
     * @param maxRadius
     */
    private void drawRadius(Graphics g, int x, int y, double angle, int minRadius, int maxRadius) {
        float sine   = (float)Math.sin(angle);
        float cosine = (float)Math.cos(angle);

        int dxmin = (int)(minRadius * sine);
        int dymin = (int)(minRadius * cosine);

        int dxmax = (int)(maxRadius * sine);
        int dymax = (int)(maxRadius * cosine);
        
        g.drawLine(x+dxmin, y+dymin, x+dxmax, y+dymax);
    }
}